
import java.util.ArrayList;
import java.util.Random;
import org.jgap.InvalidConfigurationException;
import org.jgap.gp.CommandGene;
import org.jgap.gp.GPFitnessFunction;
import org.jgap.gp.GPProblem;
import org.jgap.gp.IGPProgram;
import org.jgap.gp.function.Add;
import org.jgap.gp.function.Divide;
import org.jgap.gp.function.Exp;
import org.jgap.gp.function.Multiply;
import org.jgap.gp.function.Pow;
import org.jgap.gp.function.Sine;
import org.jgap.gp.function.Subtract;
import org.jgap.gp.function.Tangent;
import org.jgap.gp.impl.DefaultGPFitnessEvaluator;
import org.jgap.gp.impl.DeltaGPFitnessEvaluator;
import org.jgap.gp.impl.FitnessProportionateSelection;
import org.jgap.gp.impl.GPConfiguration;
import org.jgap.gp.impl.GPGenotype;
import org.jgap.gp.impl.GPPopulation;
import org.jgap.gp.impl.GPProgram;
import org.jgap.gp.impl.ProgramChromosome;
import org.jgap.gp.impl.TournamentSelector;
import org.jgap.gp.terminal.Terminal;
import org.jgap.gp.terminal.Variable;
import org.jgap.impl.WeightedRouletteSelector;

/**
 *
 * @author Heitor
 */
public class CurvaProblemV6 extends GPProblem {

    /*
     * 
     * Criação das variaveis, que vão ser usada como parametros da GP
     * 
     */
    public static boolean initGrow = false;
    public static boolean initFull = false;
    public static boolean initRampedHalfAndHalf = false;
//     
    public static Float[] x = new Float[40];
    public static float[] y = new float[40];
    public static Float[] z;
//    // number of variables to use (output variable is excluded)
    public static int numVariables;
//    // the variables to use (of size numVariables)
    public static Variable[] variables;
    // variable name
    public static String[] variableNames;
    // index of the output variable
   
//
    public static boolean foundBeastIndiv = false;
//    // standard GP parameters
    public static int minInitDepth = 2;
    public static int maxInitDepth = 4;
    public static int populationSize = 1000;
    public static int maxCrossoverDepth = 8;
    public static int numEvolutions = 1800;
    public static int maxNodes = 50;
    public static float reproductionProb = 0.1f; // float
    public static float mutationProb = 0.01f; // float
    public static float crossoverProb = 0.9f; // float
    public static int tournamentSelectorSize = 0;
//    // valores minimos e valores máximos para os terminais
    public static double minValue = 1.0;
    public static double maxValue = 12.0;
//    // retornar apenas números inteiros 
    public static boolean returnOnlyInteger = true;

    private static boolean useRowFitness = false;
    private static boolean useAdjustedFitness = false;
    private static boolean useNormalizedFitness = false;
    private static GPGenotype genotype;
    private static double truncationSelectorPorCent;
    private static int outputVariable;

    public CurvaProblemV6(GPConfiguration a_conf) 
            throws InvalidConfigurationException {
        super(a_conf);
    }

    /*
     * Este método é usado para configurar os comandos e os terminais 
     * que podem ser usados ​​para resolver o problema.
     */
    @Override
    public GPGenotype create() throws InvalidConfigurationException {

        GPConfiguration conf = getGPConfiguration();

        //primeiramente definimos os tipos de retornos da GP
        Class[] types = new Class[]{
            CommandGene.FloatClass};//somente 1 agumento pq não vou usar ADF
        Class[][] argTypes = new Class[][]{
            {}}; // vazio porque não vou usar adf

        //aki definimos o conjunto de comandos disponíveis GP.
        ArrayList<CommandGene> commandsList = new ArrayList<CommandGene>();
        commandsList.add(new Add(conf, CommandGene.FloatClass));
        commandsList.add(new Subtract(conf, CommandGene.FloatClass));
        commandsList.add(new Multiply(conf, CommandGene.FloatClass));
        commandsList.add(new Divide(conf, CommandGene.FloatClass));
        commandsList.add(new Seno(conf, CommandGene.FloatClass));
        //commandsList.add(new Cosseno(conf, CommandGene.FloatClass));
        //commandsList.add(new Tangente(conf, CommandGene.FloatClass));
        //commandsList.add(new Potencia(conf, CommandGene.FloatClass));
        commandsList.add(new Terminal(conf, CommandGene.FloatClass, 
                minValue, maxValue, returnOnlyInteger));

        CommandGene[] commands = new CommandGene[commandsList.size()];
        commandsList.toArray(commands);


        //criação do nodeset
        CommandGene[][] nodeSets = 
                new CommandGene[1][numVariables + commands.length];

        //cria e adiciona as variaveis no nodeSets.
        variables = new Variable[numVariables];
        int indexVariables = 0;
        for (int i = 0; i <= numVariables; i++) {

            String name = variableNames[i];
            if (i != outputVariable) {
                if (variableNames != null && variableNames.length > 0) {
                    name = variableNames[i];
                }
                variables[indexVariables] = 
                        new Variable(conf, name, CommandGene.FloatClass);
                nodeSets[0][indexVariables] = variables[indexVariables];
                System.out.println("Variáveis: " + variables[indexVariables]);
                indexVariables++;
            }
        }
        // adiciona no nodeSet as funcoes e terminais
        for (int i = 0; i < commands.length; i++) {
            System.out.println("function1: " + commands[i]);
            nodeSets[0][i + numVariables] = commands[i];
        }

        //inicia as variaveis X e Y para descobrir a função 4*sin(x);
        System.out.println("Fitiness Cases:");
        Random random = new Random();
        for (int i = 0; i < 40; i++) {
            float r = 4.0f * (random.nextFloat());

            x[i] = new Float(r);
            y[i] = (float) (4 * (Math.sin(r)));
            System.out.println(i + ") " + x[i] + "   " + y[i]);

        }
        System.out.println("");
        
        int[] minDepths = new int[1];
        int[] maxDepths = new int[1];
        boolean[] fullMode = new boolean[1];

        if (initGrow) {
            fillArrayWithInt(minDepths, minInitDepth);
            fillArrayWithInt(maxDepths, maxInitDepth);
            fillArrayWithBool(fullMode, false);
//            ret

            IGPProgram[] progs = new IGPProgram[populationSize];
            for (int i = 0; i < populationSize; i++) {
                GPProgram prog = validateDepths(conf, types, argTypes, 
                        nodeSets, minDepths, maxDepths, fullMode);
//                System.out.println("");
                progs[i] = prog;
            }
            GPGenotype gn = GPGenotype.randomInitialGenotype(conf, types, 
                    argTypes, nodeSets, minDepths, maxDepths, maxNodes, 
                    fullMode, true);
            gn.getGPPopulation().clear();
            gn.getGPPopulation().setGPPrograms(progs);
            return gn;

        }
        if (initFull) {
            fillArrayWithInt(minDepths, maxInitDepth);
            fillArrayWithInt(maxDepths, maxInitDepth);
            fillArrayWithBool(fullMode, true);

            IGPProgram[] progs = new IGPProgram[populationSize];
            for (int i = 0; i < populationSize; i++) {

                GPProgram prog = new GPProgram(conf, types, argTypes, nodeSets, 
                        minDepths, maxDepths, maxNodes);
                prog.growOrFull(maxInitDepth, false, maxNodes, fullMode, 100);
// 
//                System.out.println("");
                progs[i] = prog;
            }
            GPGenotype gn = GPGenotype.randomInitialGenotype(conf, types, 
                    argTypes, nodeSets, minDepths, maxDepths, maxNodes, 
                    fullMode, true);
            gn.getGPPopulation().clear();
            gn.getGPPopulation().setGPPrograms(progs);
            return gn;
        }
        if (initRampedHalfAndHalf) {

            fillArrayWithInt(minDepths, minInitDepth);
            fillArrayWithInt(maxDepths, maxInitDepth);

            IGPProgram[] progs = new IGPProgram[populationSize];

            //divide a população igualmente em um numero max de profundidade 
            //menos numero minimo de profundidade.
            int maxDepth = maxInitDepth;
            //numero de divisões de profundidade.
            int numDivTree = (maxDepth - minInitDepth) + 1;
            Integer popSizeEachDiv = populationSize / numDivTree;
            Integer dif = 0;

            if (popSizeEachDiv * numDivTree != populationSize) {
                dif = (popSizeEachDiv * numDivTree) - populationSize;
            }
            int indexPopulation = 0;
            int maxDepthtoUse = minInitDepth;
            for (int j = 1; j <= numDivTree; j++) {
                if (j == numDivTree) {
                    if (dif < 0) {
                        dif = (dif * (-1));
                    }
                    popSizeEachDiv += dif;
                }

                for (int i = 0; i < popSizeEachDiv; i++) {
                    //capa população que foi dividida recebe 
                    //um valor de produndidade
                    fillArrayWithInt(maxDepths, maxDepthtoUse);
                    int maxnodes = ((int) (Math.pow(2, (
                            maxDepthtoUse + 1)) - 1));

                    //cria metade da população pelo método grow e metade 
                    //pelo método full.
                    if (i % 2 == 0) {
                        //cria um individuo pelo metodo full. 
                        GPProgram prog = new GPProgram(conf, types, argTypes, 
                                nodeSets, minDepths, maxDepths, maxnodes);
                        prog.growOrFull(maxDepthtoUse, false, 
                                maxnodes, new boolean[]{true}, 100);
                        progs[indexPopulation] = prog;

//                        System.out.println("");
                        indexPopulation += 1;
                    } else {
                        //cria um individuo pelo metodo grow.
                        GPProgram prog = validateDepths(conf, types, argTypes,
                                nodeSets, minDepths, maxDepths, fullMode, 
                                maxDepthtoUse, maxnodes);
                        progs[indexPopulation] = prog;
//                  
//                        System.out.println("");
                        indexPopulation += 1;
                    }
                }
                maxDepthtoUse++;//incrementa o numero da profundidade maxima.
            }
            GPGenotype gn = GPGenotype.randomInitialGenotype(conf, types, 
                    argTypes, nodeSets, minDepths, maxDepths, 
                    maxNodes, fullMode, true);
            gn.getGPPopulation().clear();
            gn.getGPPopulation().setGPPrograms(progs);
            return gn;
        } else {
            //default - não usado
            return GPGenotype.randomInitialGenotype(conf, types, argTypes, 
                    nodeSets, maxNodes, true);
        }

    }

    public GPProgram validateDepths(GPConfiguration conf, Class[] types, 
            Class[][] argTypes, CommandGene[][] nodeSets, int[] minDepths, 
            int[] maxDepths, boolean[] fullMode) 
            throws InvalidConfigurationException {

        GPProgram prog = new GPProgram(conf, types, argTypes, nodeSets, 
                minDepths, maxDepths, maxNodes);
        for (int i = 0; i < 100; i++) {
            prog.growOrFull(maxInitDepth, true, maxNodes, fullMode, 100);
            if (prog.getChromosome(0).getDepth(0) <= 
                    maxInitDepth && prog.getChromosome(0).getDepth(0)
                    >= minInitDepth) {
                break;
            }

        }
        return prog;
    }

    public GPProgram validateDepths(GPConfiguration conf, Class[] types, 
            Class[][] argTypes, CommandGene[][] nodeSets, int[] minDepths, 
            int[] maxDepths, boolean[] fullModen, int maxDepthtoUse, 
            int maxnodes) throws InvalidConfigurationException {

        GPProgram prog = new GPProgram(conf, types, argTypes, 
                nodeSets, minDepths, maxDepths, maxnodes);
        for (int i = 0; i < 100; i++) {
            prog.growOrFull(maxDepthtoUse, true, maxnodes, 
                    new boolean[]{false}, 100);
            if (prog.getChromosome(0).getDepth(0) <= 
                    maxDepthtoUse && prog.getChromosome(0).getDepth(0) 
                    >= minInitDepth) {
                break;
            }

        }
        return prog;
    }

    public static void main(String[] args) 
            throws InvalidConfigurationException {

        System.out.println("Fórmula a descobir: 4*sin(X) ");

        //parametros utilizados nas variações dos testes
        //-----------------------------------
        maxValue = 10;//valor máximo dos terminais
        minValue = -10;//valor minimo dos terminais
        maxInitDepth = 8;
        minInitDepth = 2;
        crossoverProb = 0.9f;
        reproductionProb = 0.1f;
        mutationProb = 0.01f;
        populationSize = 100;
        tournamentSelectorSize =5;
        truncationSelectorPorCent = 0;
        initGrow = true;
        //initFull = true;
        //initRampedHalfAndHalf = true;
        //------------------------------------------------

        //parametros não variaveis nos testes.
        //---------------------------------------
        foundBeastIndiv = true;
        variableNames = "X,Y".split(",");
        numVariables = 1;
        outputVariable = 1;
        //depende da profundidade máxima
        maxNodes = ((int) (Math.pow(2, (maxInitDepth + 1)) - 1));
        numEvolutions = 3000;
        returnOnlyInteger = true;
        useRowFitness = true;//raw fitness para todos os testes
        maxCrossoverDepth = 4;
        //useAdjustedFitness = true;
        //useNormalizedFitness = true;
        if (initFull) {
            minInitDepth = maxInitDepth;
        }

        GPConfiguration config = new GPConfiguration();
        //config.setCrossoverMethod(null);
        config.setDynamizeArityProb(0);
        config.setCrossoverProb(crossoverProb);
        config.setMutationProb(mutationProb);
        config.setReproductionProb(reproductionProb);
        config.setMaxInitDepth(maxInitDepth);
        config.setMinInitDepth(minInitDepth);
        config.setPopulationSize(populationSize);
        config.setMaxCrossoverDepth(maxCrossoverDepth);
        if (useRowFitness) {
            //menor valor é o melhor
            config.setGPFitnessEvaluator(new DeltaGPFitnessEvaluator());
        }
        if (useNormalizedFitness || useAdjustedFitness) {
            //maior valor é o melhor 
            config.setGPFitnessEvaluator(new DefaultGPFitnessEvaluator());
        }

        if (tournamentSelectorSize > 0) {
            config.setSelectionMethod(new TournamentSelectorMy(
                    tournamentSelectorSize));
        }
        if ((truncationSelectorPorCent > 0)) {
            config.setSelectionMethod(new TruncationSelector(
                    truncationSelectorPorCent));
        }

        config.setFitnessFunction(new CurvaProblemV6.FuncaoFitness());
        config.setStrictProgramCreation(true);

        GPProblem problem = new CurvaProblemV6(config);

        GPGenotype gp = problem.create();
        //mostra as informações do status no console.
        gp.setVerboseOutput(false);



        System.out.println("Começou a marcação de tempo!");
        long start = System.currentTimeMillis();
        double bestFitness = 0.0;
        if (useRowFitness) {
            bestFitness = Double.MAX_VALUE;
        }
        if (useAdjustedFitness || useNormalizedFitness) {
            bestFitness = Double.MIN_VALUE;
        }
        String bestIndv = "";
        int bestDepth = 0;
        int numNos = 0;
        int bestGen = 0;
        int gen = 0;

        if (useNormalizedFitness) {
            genotype = gp;
        }
        for (int generation = 0; generation < numEvolutions; generation++) {
            System.out.println("Geração:" + generation + " População total: " 
                    + gp.getGPPopulation().getGPPrograms().length);
            gp.evolve();//evolui uma população
            gp.calcFitness();//calcula o fitness de todas os indivíduos 
            //presentes na população
          
            GPPopulation population = gp.getGPPopulation();
            //Determina o mais apto da população
            IGPProgram fittest = population.determineFittestProgram();
            ProgramChromosome chrom = null;
            try {
                chrom = fittest.getChromosome(0);
            } catch (Exception ex) {
                fittest = 
                  (IGPProgram) population.determineFittestChromosomes(1).get(0);
                chrom = fittest.getChromosome(0);
            }
            String indv = chrom.toStringNorm(0);
            double fitnes = fittest.getFitnessValue();
            //            System.out.println("Melhor Indivíduo: " +indv);
            //            System.out.println("Valor do Fitnes: " +fitnes);
            gen = generation;
            if (useRowFitness) {
                if (fitnes < bestFitness) {
                    bestFitness = fitnes;
                    bestIndv = indv;
                    bestGen = generation;
                    bestDepth = chrom.getDepth(0);
                    numNos = chrom.size();
                    System.out.println("Geração:" + generation);
                    System.out.println("Melhor Indivíduo: " + indv);
                    System.out.println("Valor do Fitnes: " + fitnes);
                    System.out.println("Profundidade: " + chrom.getDepth(0));
                    System.out.println("Nos: " + numNos);
                }
            }
            if (useNormalizedFitness || useAdjustedFitness) {
                if (fitnes > bestFitness) {
                    bestFitness = fitnes;
                    bestIndv = indv;
                    System.out.println("Geração:" + generation);
                    System.out.println("Melhor Indivíduo: " + indv);
                    System.out.println("Valor do Fitnes: " + fitnes);
                    System.out.println("Profundidade:" + chrom.getDepth(0));
                }
            }

            if (foundBeastIndiv) {
                if (useRowFitness) {
                    if (bestFitness == 0) {
                        System.out.println("Geração:" + generation);
                        System.out.println("Indivíduo Perfeito");
                        System.out.println("Parando a geração");
                        break;
                    }
                }
                if (useNormalizedFitness || useAdjustedFitness) {
                    if (bestFitness == 1.0) {
                        System.out.println("Geração:" + generation);
                        System.out.println("Indivíduo Perfeito");
                        System.out.println("Parando a geração");
                        break;
                    }
                }

            }

        }
        long finish = System.currentTimeMillis();
        System.out.println("Gerações evoluidas: " + (gen + 1));
        //gp.outputSolution(gp.getAllTimeBest());
        long total = finish - start;
        String time = String.format("%5.4f", (finish - start) / 1000.0f);
        System.out.println("");
        System.out.println("Melhor indivíduo encontrado na Geração " + 
                (bestGen + 1));
        System.out.println("Indivíduo: " + bestIndv);
        System.out.println("Fitness: " + bestFitness);
        System.out.println("Profundidade: " + bestDepth);
        System.out.println("Nos: "  + numNos);
        System.out.println("Tempo: " + time + " segundos.");

        problem.showTree(gp.getAllTimeBest(), "mathproblem_best6.png");

    }

    //preenche uma array com o inteiro passado.
    private void fillArrayWithInt(int[] minDepths, int minInitDepth) {
        for (int i = 0; i < minDepths.length; i++) {
            minDepths[i] = minInitDepth;
        }
    }

    private void fillArrayWithBool(boolean[] fullMode, boolean b) {
        for (int i = 0; i < fullMode.length; i++) {
            fullMode[i] = b;
        }
    }

    public static class FuncaoFitness extends GPFitnessFunction {

        private static final long serialVersionUID = 1L;
        private double sunAjustFitOfAll;

        @Override
        protected double evaluate(IGPProgram igpp) {
            //System.out.println("foi 100");
            if (useNormalizedFitness) {
                sunAjustFitOfAll = sumAdjustFitOfAll();
                return normalizedFitness(igpp);
            }
            if (useAdjustedFitness) {
                return adjustedFitness(igpp);
            } else {
                return rawFitness(igpp);
            }
        }

        private double adjustedFitness(IGPProgram indv) {
            double standardizedfitness = rawFitness(indv);
            Double toreturn = (1 / (1 + standardizedfitness));
            if (Double.isInfinite(toreturn) || Double.isNaN(toreturn)) {
                toreturn = 0.0001;
            }
            return toreturn;
        }

        private double normalizedFitness(IGPProgram indv) {
            //System.out.println(sunAjustFitOfAll);
            double fitval = adjustedFitness(indv) / sunAjustFitOfAll;
            if (Double.isInfinite(fitval) || Double.isNaN(fitval)) {
                fitval = 0.0001;
            }
            return fitval;
        }

        private double rawFitness(IGPProgram indv) {
            Object[] noargs = new Object[0];
            Double error = 0.0;

            for (int i = 0; i < 40; i++) {
                variables[0].set(x[i]);

                try {

                    double result = indv.execute_float(0, noargs);

                    error += Math.abs(result - y[i]);

                    if (Double.isInfinite(result) || Double.isNaN(result)) {

                        return Double.MAX_VALUE;
                    }

                } catch (ArithmeticException ex) {
                    System.out.println("x = " + x[i].floatValue());
                    System.out.println(indv);
                    throw ex;
                }
            }
            if (error < 0.001) {
                error = 0.0d;
            }
            return error;
        }

        private double sumAdjustFitOfAll() {
            double sumAll = 0;
            for (int i = 0; i < 
                    genotype.getGPPopulation().getGPPrograms().length; i++) {
                sumAll += adjustedFitness(
                        genotype.getGPPopulation().getGPPrograms()[i]);
            }
            return sumAll;
        }
    }
}
